package com.scpii.json.rpc;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.scpii.json.JSONException;
import com.scpii.json.JSONUtil;

/**
 * Implementation of JSON-RPC over HTTP/POST
 */
public class JSONRPCHttpClient extends JSONRPCClient {

	private Logger log = Logger.getLogger(JSONRPCHttpClient.class);

	/*
	 * HttpClient to issue the HTTP/POST request
	 */
	private HttpClient httpClient;
	/*
	 * Service URI
	 */
	private String serviceUri;

	// HTTP 1.0
	private static final ProtocolVersion PROTOCOL_VERSION = new ProtocolVersion(
			"HTTP", 1, 0);

	/**
	 * Construct a JsonRPCClient with the given httpClient and service uri
	 * 
	 * @param client
	 *            httpClient to use
	 * @param uri
	 *            uri of the service
	 */
	public JSONRPCHttpClient(HttpClient cleint, String uri) {
		httpClient = cleint;
		serviceUri = uri;
	}

	/**
	 * Construct a JsonRPCClient with the given service uri
	 * 
	 * @param uri
	 *            uri of the service
	 */
	public JSONRPCHttpClient(String uri) {
		this(new DefaultHttpClient(), uri);
	}

	@SuppressWarnings("rawtypes")
	protected JSONRPCResponse doJSONRequest(String jsonRequest)
			throws JSONRPCException {
		// Create HTTP/POST request with a JSON entity containing the request
		HttpPost request = new HttpPost(serviceUri);
		HttpParams params = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(params,
				getConnectionTimeout());
		HttpConnectionParams.setSoTimeout(params, getSoTimeout());
		HttpProtocolParams.setVersion(params, PROTOCOL_VERSION);
		request.setParams(params);

		if (_debug) {
			log.debug("Request: " + jsonRequest.toString());
		}

		HttpEntity entity;

		try {
			if (encoding.length() > 0) {
				entity = new JSONEntity(jsonRequest, encoding);
			} else {
				entity = new JSONEntity(jsonRequest);
			}
		} catch (UnsupportedEncodingException e1) {
			throw new JSONRPCException("Unsupported encoding", e1);
		}
		request.setEntity(entity);

		try {
			// Execute the request and try to decode the JSON Response
			long t = System.currentTimeMillis();
			HttpResponse response = httpClient.execute(request);

			t = System.currentTimeMillis() - t;
			String responseString = EntityUtils.toString(response.getEntity());

			responseString = responseString.trim();

			if (_debug) {
				log.debug("Response: " + responseString);
			}

			JSONRPCResponse rpcResponse = null;
			Object obj = JSONUtil.deserialize(responseString);
			if (obj instanceof Map) {
				Map json = (Map) obj;
				if (json.get("error") != null && json.get("error") != "null") {
					throw new JSONRPCException(json.get("error"));
				} else {
					rpcResponse = new JSONRPCResponse();
					rpcResponse.setId(Integer
							.valueOf(json.get("id").toString()));
					rpcResponse.setResult(json.get("result"));
				}
			}
			return rpcResponse;
		}
		// Underlying errors are wrapped into a JSONRPCException instance
		catch (ClientProtocolException e) {
			throw new JSONRPCException("HTTP error", e);
		} catch (IOException e) {
			throw new JSONRPCException("IO error", e);
		} catch (JSONException e) {
			throw new JSONRPCException("Invalid JSON response", e);
		}
	}
}
